//065959264	Limor Bagizada	040199770	Sarit Chicotay

package Models;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;

import org.omg.CORBA.DynAnyPackage.InvalidValue;
import General.Perplexity;
import Base.Corpus;
import Base.NGram;

/**
 * 
 * The BaseModel class contains the basic attributes and functionality of a model
 *
 */
public abstract class BaseModel implements Model {

     // the full content the model is based on
	 protected ArrayList<String> content;
	 
	 // training corpus
	 protected Corpus training;
	 
	 // test corpus
	 protected Corpus trainingTest;
	 
	 //contains the words that are appeared in test but are not appeared in training
	 protected TreeSet<NGram> wordsNotInTraining;
	 
	// for performance improvements - keep calculated probabilities (since all events with the same count (r) have the same probability)
	// key - count (r)
	// value - the probability
	protected TreeMap<Integer, Double> r_probabilities;
	 
	 /** 
	  * Constructor
	  * @param filePath - Initialize model from the given file.
	  * @param trainingFactor - Given factor for training/test separation
	  * @throws IOException
	 * @throws InvalidValue 
	  */
	 public BaseModel(ArrayList<String> content, double trainingFactor, int nGram) throws InvalidValue
	 {
		 // read article's content
		 this.content = content;
				 
		 // create training/test corpus by separating the content according to the training factor
		 if (trainingFactor < 0 || trainingFactor > 1)
			 throw new InvalidValue("Invalid factor for training corpus. Value should be between 0 and 1.");
		 
		 CreateCorpus(trainingFactor, nGram);
		 
		 wordsNotInTraining = new TreeSet<NGram>();
		 for (NGram event : trainingTest.getVocubularyKeySet()){
				if (!training.contain_word(event))
				{
					wordsNotInTraining.add(event);
				}
		}
		 
		 r_probabilities = new TreeMap<Integer, Double>();
	 }
	 
	 /**
	  * Creates training/test corpus by separating the content according to the training factor
	  * @param trainingPercent - factor of training corpus, the rest is used for testing
	  */
	 private void CreateCorpus(double trainingFactor, int nGram)
	 {
		 List<String> words;
		 
		 // calculate the expected size of training set according to the given factor
		 int trainingSize = (int)Math.ceil(content.size() * trainingFactor);
		 
		 // create training corpus from initial content of calculated size 
		 words = content.subList(0, trainingSize);
		 training = new Corpus(words, nGram);
		 
		 // create test corpus from the rest of the content
		 words = content.subList(trainingSize, content.size());		 
		 trainingTest = new Corpus(words, nGram);
	 }

	 //  P_d(x) - calculate the probability of a given word according to the appropriate discounting method (for example, Lidstone or HeldOut)
	 public double CalculateProbability(NGram event) throws Exception
	 {
		 if (event.getNGram() != this.training.getNGram())
		 {
			 //throw new Exception("Invalid n-Gram event");
			 return 0;
		 }
		 
		 // get count of the given word in training set
		 int r = training.getCount(event); 
		 
		// check if the probability for r was already calculated - 
		// 1) if so - return this probability
		if (r_probabilities.containsKey(r))
			return r_probabilities.get(r);
		
		// 2) else - calculate
		double result = CalculateProbability(r);
		// save calculated probability
		r_probabilities.put(r, result);
		return result;
		
	 }	
	 	 
	// if r==0 return a set words which are contained in test but are not contained in training
 	// else return all the words that appears r times in the training
	public TreeSet<NGram> words_count_r(int r) {
		TreeSet<NGram> r_words=null;
		if (r==0){
			r_words = wordsNotInTraining;	
		}
		else
			r_words = training.words_with_count_r(r);//all the words that appears r times in the training
		return r_words;
	}
	
	// get training size
	public int GetTrainingCorpusSize() { return training.getSize(); }
	
	// get test size
	public int GetTestCorpusSize(){ return trainingTest.getSize(); }
		
	public double debugging() throws Exception {
		double result=0;
		double temp=0;
		for (NGram event : training.getVocubularyKeySet()){
			temp = CalculateProbability(event);
			result+=temp;
		}
		int N_0 = this.return_N_r(0);
		if (N_0 > 0)
		{
			 // get some word that was not in the training
			// NGram word = wordsNotInTraining.first();
			 
			 // multiply the probability of such a word with N0
			 temp = CalculateProbability(0);
			 result+= temp*N_0;
		}
		return result;
	}
	
	// number of events (words) that appears r times in the corpus 
	public int return_N_r(int r) {
		int result=0;
		if (r==0)
			result = training.getVocabularySize()-training.getUnigramVocubularyKeySet().size();
		else
			result = training.words_with_count_r(r).size();
		
		return result;
	}
	
	public double get_perplexity(Corpus test) throws Exception{
		Perplexity p = new Perplexity(this);
		double result_perplexity = p.calculatePerplexity(test);
		return result_perplexity;
	}
		 
}
